package com.lqwu.mallchatserver.user.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lqwu.mallchatserver.chat.service.ChatService;
import com.lqwu.mallchatserver.common.domain.vo.request.CursorPageBaseReq;
import com.lqwu.mallchatserver.common.domain.vo.request.PageBaseReq;
import com.lqwu.mallchatserver.common.domain.vo.response.CursorPageBaseResp;
import com.lqwu.mallchatserver.common.domain.vo.response.PageBaseResp;
import com.lqwu.mallchatserver.common.event.UserApplyEvent;
import com.lqwu.mallchatserver.common.exception.BusinessException;
import com.lqwu.mallchatserver.common.utils.AssertUtil;
import com.lqwu.mallchatserver.common.utils.CursorUtils;
import com.lqwu.mallchatserver.user.mapper.UserFriendMapper;
import com.lqwu.mallchatserver.user.model.dto.*;
import com.lqwu.mallchatserver.user.model.entry.RoomFriend;
import com.lqwu.mallchatserver.user.model.entry.User;
import com.lqwu.mallchatserver.user.model.entry.UserApply;
import com.lqwu.mallchatserver.user.model.entry.UserFriend;
import com.lqwu.mallchatserver.user.model.enums.ApplyReadStatusEnum;
import com.lqwu.mallchatserver.user.model.enums.ApplyStatusEnum;
import com.lqwu.mallchatserver.user.model.enums.MessageTypeEnum;
import com.lqwu.mallchatserver.user.model.vo.FriendApplyResp;
import com.lqwu.mallchatserver.user.model.vo.FriendCheckResp;
import com.lqwu.mallchatserver.user.model.vo.FriendResp;
import com.lqwu.mallchatserver.user.model.vo.FriendUnreadResp;
import com.lqwu.mallchatserver.user.service.FriendService;
import com.lqwu.mallchatserver.user.service.RoomService;
import com.lqwu.mallchatserver.user.service.UserApplyService;
import com.lqwu.mallchatserver.user.service.UserService;
import com.lqwu.mallchatserver.user.service.adapter.FriendAdapter;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class FriendServiceImpl extends ServiceImpl<UserFriendMapper, UserFriend> implements FriendService {
    @Resource
    private UserApplyService userApplyService;
    @Resource
    private ApplicationEventPublisher publisher;
    @Resource
    @Lazy
    private FriendService friendService;
    @Resource
    private RoomService roomService;
    @Resource
    private ChatService chatService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private UserService userService;
    @Resource
    private TransactionTemplate transactionTemplate;

    /**
     * 检查用户是否为好友
     *
     * @param currentUid
     * @param req
     * @return
     */
    @Override
    public FriendCheckResp check(Long currentUid, FriendCheckReq req) {
        // 校验参数
        List<Long> uidList = req.getUidList();
        AssertUtil.isTrue(uidList.size() < 50, "检查的列表长度过长");
        FriendCheckResp resp = new FriendCheckResp();
        // 查出当前用户所有的好友的id集合
        List<Long> friendUidList = getFriendUidList(currentUid);
        // 遍历检查,  将结果保存起来
        List<FriendCheckResp.FriendCheck> checkList = resp.getCheckList();
        for (Long friendUid : uidList) {
            FriendCheckResp.FriendCheck check = new FriendCheckResp.FriendCheck();
            check.setUid(friendUid);// 用户id
            check.setIsFriend(friendUidList.contains(friendUid));// 是否是好友
            checkList.add(check);
        }
        resp.setCheckList(checkList);
        return resp;
    }

    /**
     * 获取当前用户的所有好友id
     * @param currentUid
     * @return
     */
    private List<Long> getFriendUidList(Long currentUid) {
        List<Long> friendUidList = this.lambdaQuery()
                .eq(UserFriend::getUid, currentUid)
                .select(UserFriend::getFriendUid)
                .list()
                .stream().map(UserFriend::getFriendUid)
                .collect(Collectors.toList());
        return friendUidList;
    }

    /**
     * 好友申请
     * @param currentUid
     * @param req
     */
    @Override
    public void apply(Long currentUid, FriendApplyReq req) {
        // 参数校验
        Long targetUid = req.getTargetUid();
        AssertUtil.isTrue(targetUid != null, "请选择要添加的好友");
        // 判断是否已为好友
        if (getFriendUidList(currentUid).contains(targetUid)) return;
        // 是否有待审批的申请记录(自己的)
        UserApply friendApproving = userApplyService.getFriendApproving(currentUid, targetUid);
        if (friendApproving != null){
            log.info("请勿重复申请");
            return;
        }
        // 是否有待审批的申请记录(对方的)
        UserApply friendApproving2 = userApplyService.getFriendApproving(targetUid, currentUid);
        if (friendApproving2 != null){
            // 帮该用户发送同意
            friendService.applyApprove(currentUid,new FriendApproveReq(targetUid));
            return;
        }
        // 添加申请记录
        UserApply builder = userApplyService.builder(currentUid, targetUid, req.getMsg());
        boolean save = userApplyService.save(builder);
        AssertUtil.isTrue(save, "申请创建失败");
        // 申请事件发送
        publisher.publishEvent(new UserApplyEvent(this, builder));
    }

    /**
     * 分页查询好友申请
     *
     * @param request 请求
     * @return {@link PageBaseResp}<{@link FriendApplyResp}>
     */
    @Override
    public PageBaseResp<FriendApplyResp> pageApplyFriend(Long uid, PageBaseReq request) {
        // 1.获取好友申请列表
        IPage<UserApply> userApplyIPage = userApplyService.friendApplyPage(uid, request.plusPage());
        if (CollectionUtil.isEmpty(userApplyIPage.getRecords())) {
            return PageBaseResp.empty();
        }
        // 2.将消息状态改为已读
        List<UserApply> records = userApplyIPage.getRecords();
        List<UserApply> newRecords = new ArrayList<>();
        for (UserApply record : records) {
            record.setReadStatus(ApplyReadStatusEnum.READ.getCode());
            newRecords.add(record);
        }
        boolean updated = userApplyService.updateBatchById(newRecords);
        AssertUtil.isTrue(updated,"消息阅读状态更新失败");
        // 3.封装返回值
        // 将UserApply的List转FriendApplyResp的List
        List<FriendApplyResp> friendApplyRespList = records.stream().map(userApply -> {
            FriendApplyResp friendApplyResp = new FriendApplyResp();
            friendApplyResp.setApplyId(userApply.getId());
            friendApplyResp.setUid(userApply.getUid());
            friendApplyResp.setType(userApply.getType());
            friendApplyResp.setMsg(userApply.getMsg());
            friendApplyResp.setStatus(userApply.getStatus());
            return friendApplyResp;
        }).collect(Collectors.toList());
        // 构造成游标翻页返回值
        return PageBaseResp.init(userApplyIPage, friendApplyRespList);
    }

    /**
     * 根据用户id获取申请未读数
     * @param uid
     * @return
     */
    @Override
    public FriendUnreadResp unread(Long uid) {
        Integer count = userApplyService.lambdaQuery()
                .eq(UserApply::getTargetId, uid)
//                .eq(UserApply::getType, ApplyTypeEnum.ADD_FRIEND.getCode())
                .eq(UserApply::getReadStatus, ApplyReadStatusEnum.UNREAD.getCode())
                .count();
        return new FriendUnreadResp(count);
    }

    /**
     * 用户同意好友申请
     * @param uid
     * @param request
     */
    @Override
    public void applyApprove(Long uid, FriendApproveReq request) {
        RLock lock = redissonClient.getLock("applyApprove:" + uid);
        try{
            // 竞争分布式锁，等待 3 秒，15 秒自动释放
            boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
            if (res){
                transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                    @Override
                    protected void doInTransactionWithoutResult(TransactionStatus status) {
                        // 抢到锁的业务才能执行 AI 调用
                        // 1.判断这好友申请是你的吗
                        UserApply byId = userApplyService.getById(request.getApplyId());
                        if (byId == null){
                            throw new BusinessException("该好友申请不存在");
                        }
                        if (!Objects.equals(byId.getTargetId(), uid)){
                            throw new BusinessException("无法同意他人的好友申请，这不属于你");
                        }
                        if (byId.getStatus().equals(ApplyStatusEnum.AGREE.getCode())) return;
                        // 2.将申请改为同意
                        boolean update = userApplyService.lambdaUpdate()
                                .eq(UserApply::getId, byId.getId())
                                .set(UserApply::getStatus, ApplyStatusEnum.AGREE.getCode())
                                .update();
                        AssertUtil.isTrue(update,"申请状态更新为同意时错误");
                        // 3.创建好友关系
                        Boolean create = createFriend(uid, byId.getUid());
                        AssertUtil.isTrue(create,"为双方建立好友关系时出错");
                        // 4.创建一个聊天房间
                        RoomFriend roomFriend = roomService.createFriendRoom(Arrays.asList(uid,byId.getUid()));
                        // 5.发送一条同意消息。。我们已经是好友了，开始聊天吧
                        // 构造消息
                        ChatMessageReq chatMessageReq = new ChatMessageReq();
                        chatMessageReq.setRoomId(roomFriend.getRoomId());
                        chatMessageReq.setMsgType(MessageTypeEnum.TEXT.getType());
                        TextMsgReq textMsgReq = TextMsgReq.builder()
                                .content("我们已经成为好友了，开始聊天吧").build();
                        chatMessageReq.setBody(textMsgReq);
                        // 发送消息
                        chatService.sendMsg(chatMessageReq,uid);
                    }
                });
            }
        } catch (Exception e) {
            throw new BusinessException("在获取分布式锁时失败了");
        } finally {
            if (lock != null && lock.isLocked()) {
                if(lock.isHeldByCurrentThread()) {
                    // 记得释放锁
                    lock.unlock();
                }
            }
        }
    }

    /**
     * 删除好友
     * @param uid
     * @param friendUid
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFriend(Long uid, Long friendUid) {
        // 1.获取两人的好友关系
        List<UserFriend> list = lambdaQuery().eq(UserFriend::getUid, uid)
                .eq(UserFriend::getFriendUid, friendUid)
                .or()
                .eq(UserFriend::getUid, friendUid)
                .eq(UserFriend::getFriendUid, uid)
                .list();
        if (ObjUtil.isNull(list)){
            // 非好友，直接返回
            log.info("没有好友关系：{},{}",uid,friendUid);
            return;
        }
        // 2.对于该好友记录删除
        List<Long> userFriendIds = list.stream().map(UserFriend::getId).collect(Collectors.toList());
        boolean removed = this.removeByIds(userFriendIds);
        AssertUtil.isTrue(removed,"删除好友关系时出错");
        // 3.禁用聊天房间
        roomService.disableFriendRoom(Arrays.asList(uid,friendUid));
    }

    /**
     * 游标翻页获取好友列表
     * @param uid
     * @param request
     * @return
     */
    @Override
    public CursorPageBaseResp<FriendResp> friendList(Long uid, CursorPageBaseReq request) {
        CursorPageBaseResp<FriendResp> friendPage = this.getFriendPage(uid, request);
        return friendPage;
    }

    /**
     * 游标分页获取好友列表
     * @param uid
     * @param request
     * @return
     */
    private CursorPageBaseResp<FriendResp> getFriendPage(Long uid, CursorPageBaseReq request) {
//        LambdaQueryWrapper<UserFriend> queryWrapper = new LambdaQueryWrapper<>();
//        // 1.根据游标字段，快速定位
//        queryWrapper.lt(UserFriend::getId,request.getCursor());
//        // 2.定义游标的方向(根据用户id大小倒排)
//        queryWrapper.orderByDesc(UserFriend::getId);
//        // 3.查询条件
//        queryWrapper.eq(UserFriend::getUid, uid);
//        // 4.查询游标分页数据
//        Page<UserFriend> page = page(request.plusPage(), queryWrapper);
//        // 5.计算游标位置
//        // 获取最后一条数据，将其id转成String，作为游标
//        String cursor = Optional.ofNullable(CollectionUtil.getLast(page.getRecords()))
//                .map(UserFriend::getId)
//                .map(String::valueOf)
//                .orElse(null);
//        // 6.判断是否为最后一页
//        Boolean isLast = isLastPage(uid,cursor,request.getPageSize(),page.getRecords().size());
//
//        return new CursorPageBaseResp<>(cursor, isLast,page.getRecords());
        //  分页查询

        // 分页查询
        CursorPageBaseResp<UserFriend> friendPage = CursorUtils.getCursorPageByMysql(this, request,
                wrapper -> wrapper.eq(UserFriend::getUid, uid), UserFriend::getId);
        // 判空
        if (CollectionUtils.isEmpty(friendPage.getList())) {
            return CursorPageBaseResp.empty();
        }
        // 获取好友用户id列表
        List<Long> friendUids = friendPage.getList()
                .stream().map(UserFriend::getFriendUid)
                .collect(Collectors.toList());
        // 获取好友列表(包含id、是否在线、名字、头像)
        List<User> userList = userService.getFriendList(friendUids);
        return CursorPageBaseResp.init(friendPage, FriendAdapter.buildFriend(friendPage.getList(), userList));
    }

    /**
     * 为双方建立好友关系
     * @param uid
     * @param targetUid
     * @return
     */
    private Boolean createFriend(Long uid, Long targetUid) {
        UserFriend userFriend1 = new UserFriend();
        userFriend1.setUid(uid);
        userFriend1.setFriendUid(targetUid);
        UserFriend userFriend2 = new UserFriend();
        userFriend2.setUid(targetUid);
        userFriend2.setFriendUid(uid);
        // 解决代理实现问题
        return friendService.saveBatch(Arrays.asList(userFriend1, userFriend2));
    }


    /**
     * 判断是否为最后一页
     * @param uid 用户id
     * @param cursor 游标
     * @param pageSize 页面大小
     * @param size 当前页数据条数
     * @return
     */
    private Boolean isLastPage(Long uid,String cursor, Integer pageSize,Integer size){
        UserFriend userFriend = new UserFriend();
        if (cursor == null){
            // 1.cursor为null，说明是第一页，看当前条数是否对上
            if (!Objects.equals(size, pageSize)) return true;
        }
        // 2.向后再查一条
        userFriend = lambdaQuery().eq(UserFriend::getUid, uid)
                .lt(UserFriend::getId, cursor)
                .orderByDesc(UserFriend::getId)
                .one();
        // 3.如果为空，则为最后一页
        return userFriend == null;
    }
}
